package lc.util;

import java.util.Hashtable;

import org.junit.Test;
import static org.junit.Assert.*;

import com.jme.math.Matrix3f;
import com.jme.math.Matrix4f;
import com.jme.math.Quaternion;
import com.jme.math.Vector2f;
import com.jme.math.Vector3f;

public final class MeshHasher {

	public final static class Triangle {
		public Vector3f[] pos = new Vector3f[3];
		public Vector3f[] normal = new Vector3f[3];
		public Vector2f[] uv = new Vector2f[3];
		public Triangle() {
			for (int i = 0; i < 3; ++i) {
				pos[i] = new Vector3f(0, 0, 0);
				normal[i] = new Vector3f(0, 0, 0);
				uv[i] = new Vector2f(0, 0);
			}
		}
	}

	private static class Tuple {
		static final Vector3f null3 = new Vector3f(0, 0, 0);
		static final Vector2f null2 = new Vector2f(0, 0);
		Vector3f p = null3;
		Vector3f n = null3;
		Vector2f t = null2;
	    public boolean equals(Object obj) {
	    	if (!(obj instanceof Tuple))
	    		return false;
	    	return p.equals(((Tuple)obj).p)
	    		&& n.equals(((Tuple)obj).n)
	    		&& t.equals(((Tuple)obj).t);
	    }
	    public int hashCode() {
	    	return p.hashCode() ^ (n.hashCode() >> 1) ^ (t.hashCode() >> 2);
	    }
	}

	public MeshHasher(boolean texcoords, boolean normals) {
		faceList = new SimpleIntArray();
		vertices = new SimpleFloatArray();
		if (texcoords) {
			this.texcoords = new SimpleFloatArray();
		}
		if (normals) {
			this.normals = new SimpleFloatArray();
		}
		tupleHash = new Hashtable<Tuple, Integer>();
	}
	public final void add(Triangle t) throws Exception {
		for (int i = 0; i < 3; ++i) {
			int j = hashIndex(t.pos[i], t.normal[i], t.uv[i]);
			faceList.add(j);
		}
	}
	public final void processTransform(Matrix4f worldInverse) {
		Vector3f tmp = new Vector3f();
		Vector3f oot = new Vector3f();
		for (int i = 0, n = vertices.logicalSize; i != n; i += 3) {
			//	transform vertex
			vertices.get3f(i, tmp);
			worldInverse.mult(tmp, oot);
			vertices.set3f(i, oot);
		}
		if (normals == null)
			return;
		Matrix3f inverseTranspose = worldInverse.invert().transpose().toRotationMatrix();
		assert(vertices.logicalSize == normals.logicalSize);
		for (int i = 0, n = normals.logicalSize; i != n; i += 3) {
			//	transform normal
			normals.get3f(i, tmp);
			inverseTranspose.mult(tmp, oot);
			oot.normalizeLocal();
			normals.set3f(i, oot);
		}
	}
	public final SimpleFloatArray getVertices() {
		return vertices;
	}
	public final SimpleFloatArray getNormals() {
		return normals;
	}
	public final SimpleFloatArray getTexcoords() {
		return texcoords;
	}
	public final SimpleIntArray getFaceList() {
		return faceList;
	}
	final int hashIndex(Vector3f p, Vector3f n, Vector2f t) {
		Tuple x = new Tuple();
		x.p = p.clone();
		if (normals != null)
			x.n = n.clone();
		if (texcoords != null)
			x.t = t.clone();
		Integer i = tupleHash.get(x);
		if (i == null) {
			//	Mmm, boxing!
			i = (Integer)(vertices.logicalSize / 3);
			vertices.add(p);
			if (normals != null)
				normals.add(n);
			if (texcoords != null)
				texcoords.add(t);
			tupleHash.put(x, i);
		}
		int ix = ((int)i)*3;
		int ix2 = ((int)i)*2;
		assert(p.x == vertices.data[ix] && p.y == vertices.data[ix+1] && p.z == vertices.data[ix+2]);
		assert(normals == null || (n.x == normals.data[ix] && n.y == normals.data[ix+1] && n.z == normals.data[ix+2]));
		assert(texcoords == null || (t.x == texcoords.data[ix2] && t.y == texcoords.data[ix2+1]));
		return (int)i;
	}

	SimpleIntArray faceList;
	SimpleFloatArray vertices;
	SimpleFloatArray normals;
	SimpleFloatArray texcoords;
	Hashtable<Tuple, Integer> tupleHash;

	/**
	 * Use this only for JUnitTest!
	 */
	public MeshHasher() {
	}

	@Test
	public final void runTest() {
		Matrix4f a = new Matrix4f();
		Quaternion q = new Quaternion();
		q.fromAngles(0.1f, 0.2f, 0.3f);
		a.setRotationQuaternion(q);
		Matrix4f b = a.invert();
		Matrix4f c = a.mult(b);
		assertTrue(checkIdentity(c));
		c = b.mult(a);
		assertTrue(checkIdentity(c));
		Matrix4f d = a.transpose();
		c = a.mult(d);
		assertTrue(checkIdentity(c));
		c = d.mult(a);
		assertTrue(checkIdentity(c));
	}

	static final boolean checkIdentity(Matrix4f mat) {
		if (Math.abs(mat.m00 - 1) > 1e-4) return false;
		if (Math.abs(mat.m11 - 1) > 1e-4) return false;
		if (Math.abs(mat.m22 - 1) > 1e-4) return false;
		if (Math.abs(mat.m33 - 1) > 1e-4) return false;

		if (Math.abs(mat.m01) > 1e-4) return false;
		if (Math.abs(mat.m02) > 1e-4) return false;
		if (Math.abs(mat.m03) > 1e-4) return false;

		if (Math.abs(mat.m10) > 1e-4) return false;
		if (Math.abs(mat.m12) > 1e-4) return false;
		if (Math.abs(mat.m13) > 1e-4) return false;

		if (Math.abs(mat.m20) > 1e-4) return false;
		if (Math.abs(mat.m21) > 1e-4) return false;
		if (Math.abs(mat.m23) > 1e-4) return false;

		if (Math.abs(mat.m30) > 1e-4) return false;
		if (Math.abs(mat.m31) > 1e-4) return false;
		if (Math.abs(mat.m32) > 1e-4) return false;

		return true;
	}
}
